#include <jni.h>
#include <EglImpl.h>
#include <android/native_window_jni.h>
#include "FrameRenderer.h"
#include "FrameCanvas.h"
#include "jni_utils.h"
#include "YUVReader.h"
#include "FrameProvider.h"
#include "FileDataProvider.h"
#include "CameraEvaluator.h"

namespace simple_renderer{

const char *JAVA_SurfaceEventWrapper = "com/vonchenchen/renderer/SurfaceEventWrapper";

#define REG_JNI_FUNC(rettype, cls, name)                                             \
  extern "C" rettype JNIEXPORT JNICALL Java_com_vonchenchen_renderer_##cls##_##name

#define EG_JNI_FUNC_TEST REG_JNI_FUNC

extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
    return InitRendererJvm(jvm);
}

static void UpdateWindow(JNIEnv *env, FrameRenderer *renderer, jobject view, jstring name){

    //create SurfaceEventWrapper
    jclass cls_event_wrapper = env->FindClass(JAVA_SurfaceEventWrapper);
    jmethodID mid_init_event_wrapper = env->GetMethodID(cls_event_wrapper, "<init>", "()V");

    jobject event_wrapper = env->NewObject(cls_event_wrapper, mid_init_event_wrapper);
    std::shared_ptr<JavaGlobalRef<jobject>> surface_event_wrapper(new JavaGlobalRef<jobject>(env, event_wrapper));

    //set renderer to SurfaceEventWrapper Object
    jmethodID mid_set_ptr = env->GetMethodID(cls_event_wrapper, "setPtr", "(J)V");
    long ptr = reinterpret_cast<long>(renderer);
    env->CallVoidMethod(surface_event_wrapper->operator*(), mid_set_ptr, (jlong)ptr);

    //set SurfaceView to SurfaceEventWrapper Object
    jmethodID mid_set_view = env->GetMethodID(cls_event_wrapper, "setSurfaceView", "(Landroid/view/SurfaceView;)V");
    env->CallVoidMethod(surface_event_wrapper->operator*(), mid_set_view, view);

    renderer->MoveIntoAndroidWindowEventWrapper(surface_event_wrapper);

    const char *cname = env->GetStringUTFChars(name, 0);

    RenderParam param;
    param.frame_type = kI420P;
    param.render_mode = FIT;

    std::shared_ptr<JavaGlobalRef<jobject>> canvas_ptr(new JavaGlobalRef<jobject>(env, view));
    //renderer reference java window
    renderer->UpdateAndroidFrameWindow(canvas_ptr, param);
    env->ReleaseStringUTFChars(name, cname);

    env->DeleteLocalRef(event_wrapper);
    env->DeleteLocalRef(cls_event_wrapper);
}

static void ClearWindow(JNIEnv *env, FrameRenderer *renderer){

    std::shared_ptr<JavaGlobalRef<jobject>> event_wrapper = renderer->MoveOutAndroidWindowEventWrapper();
    if(event_wrapper != nullptr){

        jobject j_event_wrapper = event_wrapper->operator*();
        if(j_event_wrapper) {

            jclass cls_event_wrapper = env->FindClass(JAVA_SurfaceEventWrapper);
            jmethodID mid_remove_view = env->GetMethodID(cls_event_wrapper, "removeView", "()V");
            env->CallVoidMethod(j_event_wrapper, mid_remove_view);
        }
    }
}

/****** RendererNative *******/
REG_JNI_FUNC(jlong, RendererNative, createNativeRenderer)(JNIEnv *env, jclass obj){
    auto renderer = new FrameRenderer;
    renderer->StartRender();
    return reinterpret_cast<long>(renderer);
}

REG_JNI_FUNC(void, RendererNative, destoryNativeRenderer)(JNIEnv *env, jclass obj, jlong ptr){
    auto renderer = reinterpret_cast<FrameRenderer *>(ptr);
    renderer->StopRender();
    ClearWindow(env, renderer);
    delete renderer;
}

REG_JNI_FUNC(void, RendererNative, testTrigger)(JNIEnv *env, jclass obj, jlong ptr){

}

REG_JNI_FUNC(void, RendererNative, draw)(JNIEnv *env, jclass obj, jlong ptr, jbyteArray data, jint width, jint height, jint length){

    auto renderer = reinterpret_cast<FrameRenderer *>(ptr);
    jbyte *cdata = env->GetByteArrayElements(data, 0);

    renderer->DrawFrame(width, height, (uint8_t*)cdata, length, kI420P);

    env->ReleaseByteArrayElements(data, cdata, 0);
}

REG_JNI_FUNC(void, RendererNative, drawI420)(JNIEnv *env, jclass obj, jlong ptr, jint width, jint height, jobjectArray yuvPlanes, jintArray yuvStrides){

    auto renderer = reinterpret_cast<FrameRenderer *>(ptr);

    jobject y_buff = env->GetObjectArrayElement(yuvPlanes, 0);
    jobject u_buff = env->GetObjectArrayElement(yuvPlanes, 1);
    jobject v_buff = env->GetObjectArrayElement(yuvPlanes, 2);

    int y_len = width * height;
    int u_len = y_len / 4;
    int v_len = u_len;

    // jint y_stride = env->GetObjectArrayElement(yuvStrides, 0);
    // jint u_stride = env->GetObjectArrayElement(yuvStrides, 1);
    // jint v_stride = env->GetObjectArrayElement(yuvStrides, 2);

    uint8_t *cy_buff = static_cast<uint8_t *>(env->GetDirectBufferAddress(y_buff));
    uint8_t *cu_buff = static_cast<uint8_t *>(env->GetDirectBufferAddress(u_buff));
    uint8_t *cv_buff = static_cast<uint8_t *>(env->GetDirectBufferAddress(v_buff));

    renderer->DrawFrameI420(width, height, kI420P, cy_buff, y_len, cu_buff, u_len, cv_buff, v_len);
}

REG_JNI_FUNC(void, RendererNative, setView)(JNIEnv *env, jclass obj, jlong ptr, jobject view, jint mode, jstring name){

    auto renderer = reinterpret_cast<FrameRenderer *>(ptr);
    //init surfaceview
    //UpdateWindow(env, renderer, view, name);

    //create SurfaceEventWrapper
    jclass cls_event_wrapper = env->FindClass(JAVA_SurfaceEventWrapper);
    jmethodID mid_init_event_wrapper = env->GetMethodID(cls_event_wrapper, "<init>", "()V");

    jobject event_wrapper = env->NewObject(cls_event_wrapper, mid_init_event_wrapper);
    std::shared_ptr<JavaGlobalRef<jobject>> surface_event_wrapper = std::make_shared<JavaGlobalRef<jobject>>(env, event_wrapper);

    //set renderer to SurfaceEventWrapper Object
    jmethodID mid_set_ptr = env->GetMethodID(cls_event_wrapper, "setPtr", "(J)V");
    long renderer_ptr = reinterpret_cast<long>(renderer);
    env->CallVoidMethod(surface_event_wrapper->operator*(), mid_set_ptr, (jlong)renderer_ptr);

    //set SurfaceView to SurfaceEventWrapper Object
    jmethodID mid_set_view = env->GetMethodID(cls_event_wrapper, "setSurfaceView", "(Landroid/view/SurfaceView;)V");
    env->CallVoidMethod(surface_event_wrapper->operator*(), mid_set_view, view);

    renderer->MoveIntoAndroidWindowEventWrapper(surface_event_wrapper);

    const char *cname = env->GetStringUTFChars(name, 0);

    RenderParam param;
    param.frame_type = kI420P;
    param.render_mode = FIT;

    std::shared_ptr<JavaGlobalRef<jobject>> canvas_ptr = std::make_shared<JavaGlobalRef<jobject>>(env, view);
    //renderer reference java window
    renderer->UpdateAndroidFrameWindow(canvas_ptr, param);

    //try to get surface
    //if the surfaceview already ready, the surface will be ok and egl will init.
    //else when the surfaceview ready, it will call back surfaceCreated and we init egl in surfaceCreated
    renderer->CreatWindow();

    env->ReleaseStringUTFChars(name, cname);
    env->DeleteLocalRef(event_wrapper);
    env->DeleteLocalRef(cls_event_wrapper);
    env->DeleteLocalRef(view);
}

/****** SurfaceEventWrapper *******/
REG_JNI_FUNC(void, SurfaceEventWrapper, surfaceCreated)(JNIEnv *env, jclass obj, jlong nativePtr, jobject surfaceHolder){

    //must sync
    auto renderer = reinterpret_cast<FrameRenderer *>(nativePtr);
    renderer->CreatWindow();
    LOGV("[jni surface] surfaceCreated");
}

REG_JNI_FUNC(void, SurfaceEventWrapper, surfaceChanged)(JNIEnv *env, jclass obj, jlong nativePtr, jobject surfaceHolder, jint formate, jint w, jint h){

    //must sync
    auto renderer = reinterpret_cast<FrameRenderer *>(nativePtr);
    renderer->ResizeWindow(w, h);
    LOGV("[jni surface] surfaceChanged");
}

REG_JNI_FUNC(void, SurfaceEventWrapper, surfaceDestroyed)(JNIEnv *env, jclass obj, jlong nativePtr, jobject surfaceHolder){

    //must sync
    auto renderer = reinterpret_cast<FrameRenderer *>(nativePtr);
    //renderer->DestoryWindow();
    renderer->DestorySurface();
    LOGV("[jni surface] surfaceDestroyed");
}

/****** FileProviderNative *******/
REG_JNI_FUNC(jlong, FileProviderNative, creatNativeFileProvider)(JNIEnv *env, jclass obj){
    auto file_provider = new FileDataProvider;
    return reinterpret_cast<jlong>(file_provider);
}

REG_JNI_FUNC(void, FileProviderNative, destoryNativeProvider)(JNIEnv *env, jclass obj, jlong nativePtr){
    auto file_provider = reinterpret_cast<FileDataProvider *>(nativePtr);
    file_provider->DeinitProvider();
    delete file_provider;
}

REG_JNI_FUNC(jint, FileProviderNative, initProvider)(JNIEnv *env, jclass obj, jlong nativePtr, jstring file_name){
    auto file_provider = reinterpret_cast<FileDataProvider *>(nativePtr);
    const char* cfile_name = env->GetStringUTFChars(file_name, 0);
    int ret = file_provider->InitProvider(cfile_name);
    env->ReleaseStringUTFChars(file_name, cfile_name);
    return ret;
}

REG_JNI_FUNC(jint, FileProviderNative, provideNextFrame)(JNIEnv *env, jclass obj, jlong nativePtr){
    auto file_provider = reinterpret_cast<FileDataProvider *>(nativePtr);
    int ret = file_provider->ProvideNextFrame();
    return ret;
}

REG_JNI_FUNC(void, FileProviderNative, registerRenderer)(JNIEnv *env, jclass obj, jlong nativePtr, jlong nativeRendererPtr){
    auto file_provider = reinterpret_cast<FileDataProvider *>(nativePtr);
    auto renderer = reinterpret_cast<FrameRenderer *>(nativeRendererPtr);

    file_provider->RegisterRenderer(renderer);
}

/****** CameraEvaluator *******/

REG_JNI_FUNC(void, CameraEvaNative, initGlobalContext)(JNIEnv *env, jclass obj, jobject context){

    InitAndroidContext(env, context);
}

REG_JNI_FUNC(jlong, CameraEvaNative, createCameraEva)(JNIEnv *env, jclass obj){
    auto camera_eva = new CameraEvaluator;
    camera_eva->AllocCapture();
    return reinterpret_cast<long >(camera_eva);
}

REG_JNI_FUNC(void, CameraEvaNative, destoryCameraEva)(JNIEnv *env, jclass obj, jlong nativePtr){
    auto camera_eva = reinterpret_cast<CameraEvaluator *>(nativePtr);
    camera_eva->DeallocCapture();
    delete camera_eva;
}

REG_JNI_FUNC(void, CameraEvaNative, setVideoConfig)(JNIEnv *env, jclass obj, jlong nativePtr, jobject config){
    auto camera_eva = reinterpret_cast<CameraEvaluator *>(nativePtr);
}

REG_JNI_FUNC(void, CameraEvaNative, setLocalView)(JNIEnv *env, jclass obj, jlong nativePtr, jobject view, jint mode, jstring name){

    auto camera_eva = reinterpret_cast<CameraEvaluator *>(nativePtr);

    if(camera_eva->HasRenderer()){
        //clear camera_eva's renderer
        auto renderer = camera_eva->MoveOutRenderer();
        renderer->StopRender();
        //remove and release renderer's java ref
        ClearWindow(env, renderer.get());
    }

    //create new renderer
    auto renderer = std::unique_ptr<FrameRenderer>(new FrameRenderer);
    renderer->StartRender();
    //connect with android window and window event listener
    UpdateWindow(env, renderer.get(), view, name);
    //move renderer into camera_eva
    camera_eva->MoveIntoRenderer(renderer);
}

REG_JNI_FUNC(jint, CameraEvaNative, startPreview)(JNIEnv *env, jclass obj, jlong nativePtr){

    auto camera_eva = reinterpret_cast<CameraEvaluator *>(nativePtr);
    return camera_eva->StartCapture();
}

REG_JNI_FUNC(jint, CameraEvaNative, stopPreview)(JNIEnv *env, jclass obj, jlong nativePtr){

    auto camera_eva = reinterpret_cast<CameraEvaluator *>(nativePtr);
    return camera_eva->StopCapture();
}

}